﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision;
using FarseerPhysics.Factories;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Common.ConvexHull;
using EpicBall.Camera;
using EpicBall.Levels.Classes;
using System.Diagnostics;

namespace EpicBall.ScreenSystem
{
    public class PhysicsGameScreen : GameScreen
    {
        protected World gameWorld;
        protected Body player;
        public bool isDead = false;
        protected Camera2D _camera;
        protected List<LevelObject> _levelObjects;
        protected Vector2 _startPos, _endPos;
        public int Score { get; set; }

        double currentTime = 0.0f;
        protected bool _started;
        protected String _leveltitle;

        public int _levelGold = 0;
        public int _levelSilver = 0;
        public int _levelBronze = 0;

        protected KeyboardState _keyState;
        protected Vector2 _airResistance;
        protected float _speedThreshold;
        protected int _forcePower;

        protected List<LevelObject> doors;

        protected SpriteFont basicFont;
        protected Texture2D ballTexture;
        protected Texture2D playerTopArrowTexture;


        public World World { get { return gameWorld; } }
        public Body Player { get { return player; } }

        private float _viewportWidth;
        private float _viewportHeight;

        public PhysicsGameScreen() : this(0, 0) { }
        public PhysicsGameScreen(int screenWidth, int screenHeight) : base(screenWidth, screenHeight)
        {
            TransitionOnTime = TimeSpan.FromSeconds(0.75);
            TransitionOffTime = TimeSpan.FromSeconds(0.75);
            TransitionAlpha = 0f;
        }

        public String LevelTitle { get { return _leveltitle; } }

        public virtual PhysicsGameScreen NextLevel()
        {
            return this;
        }

        /// <summary>
        /// Remove the level object from the world so it doesn't get drawn onto
        /// the screen
        /// </summary>
        public void RemoveLevelObject(LevelObject obj)
        {
            _levelObjects.Remove(obj);
        }

        public override void LoadContent()
        {
            base.LoadContent();
            Score = 0;
            currentTime = 0;
            _started = false;
            gameWorld = new World(Vector2.Zero);
            player = BodyFactory.CreateBody(gameWorld);
            player.BodyType = BodyType.Dynamic;
            basicFont = GameStack.Content.Load<SpriteFont>("basicFont");
            _levelObjects = new List<LevelObject>();
            ballTexture = GameStack.Content.Load<Texture2D>("character/ball1/face0");
            playerTopArrowTexture = GameStack.Content.Load<Texture2D>("character/player_top_arrow");
            FixtureFactory.AttachCircle(ConvertUnits.ToSimUnits(12.5), 15, player);
            _viewportHeight = GameStack.Dimensions.Y;
            _viewportWidth = GameStack.Dimensions.X;

            _startPos = Vector2.Zero;
            _camera = new Camera2D(GameStack.GraphicsDevice, this._screenWidth, this._screenHeight);
            _camera.Position = new Vector2(_viewportWidth / 2 + 10, _viewportHeight / 2 + 10);
            _camera.MinPosition = new Vector2(_viewportWidth / 2, _viewportHeight / 2);
            _camera.MaxPosition = new Vector2(this._screenWidth - _viewportWidth / 2, this._screenHeight - _viewportHeight / 2);

            doors = new List<LevelObject>();
            _airResistance = new Vector2(2f, 0);
            _speedThreshold = 15;
            _forcePower = 600;

            
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            _camera.Update(gameTime);
            CheckInput(gameTime);
            if (_started)
            {
                gameWorld.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000, (1f / 30f)));
                adjustPlayerSpeed(gameTime);
                currentTime += gameTime.ElapsedGameTime.TotalMilliseconds;
                UpdateLevelObjects(gameTime);
            }
        }


        /// <summary>
        /// Handle Player Input from keyboard
        /// </summary>
        protected virtual void CheckInput(GameTime gameTime)
        {

            _keyState = Keyboard.GetState();
            KeyboardState oldState;
            //Apply force in the arrow key direction
            Vector2 force = Vector2.Zero;
            if (_keyState.IsKeyDown(Keys.Escape))
            {
                //Redo later to handle screens bet
                GameStack.AddScreen(new MainMenu((int)GameStack.Dimensions.X, (int)GameStack.Dimensions.Y));
                MediaPlayer.Stop();
            }

            if (_started)
            {
                if (_keyState.IsKeyDown(Keys.Left))
                {
                    force.X -= _forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                if (_keyState.IsKeyDown(Keys.Right))
                {
                    force.X += _forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                if (_keyState.IsKeyDown(Keys.Up))
                {
                    force.Y -= 2 * _forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                if (_keyState.IsKeyDown(Keys.Back))
                {

                    player.Position = _startPos;
                    player.LinearVelocity = Vector2.Zero;
                    _camera.Jump2Target();
                }
                player.ApplyForce(ref force);
            }
            //Level not started yet.
            else
            {
                int cameraSpeed = 100;
                Vector2 cameraForce = Vector2.Zero;
                if (_keyState.IsKeyDown(Keys.Left))
                {
                    cameraForce += new Vector2(-cameraSpeed, 0);
                }
                if (_keyState.IsKeyDown(Keys.Right))
                {
                    cameraForce += new Vector2(cameraSpeed, 0);
                }
                if (_keyState.IsKeyDown(Keys.Up))
                {
                    cameraForce += new Vector2(0, -cameraSpeed);
                }
                if (_keyState.IsKeyDown(Keys.Down))
                {
                    cameraForce += new Vector2(0, cameraSpeed);
                }
                if (_keyState.IsKeyDown(Keys.Space))
                {
                    _started = true;
                    _camera.TrackingBody = player;
                    _camera.Jump2Target();
                }
                _camera.Position += cameraForce;
            }
            
            oldState = _keyState;
        }

        public override void Draw(GameTime gameTime)
        {
            //Set up Camera
            Matrix projection = _camera.SimProjection;
            Matrix view = _camera.SimView;
            Vector2 blah = _camera.ConvertWorldToScreen(player.Position);

            //Draw world relative to camera
            GameStack.SpriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, null, null, null, _camera.View);

            DrawBackground();
            DrawPlayer();
            DrawLevel();
            DrawText(basicFont, new Vector2(_camera.Position.X - _viewportWidth/2 + 10, _camera.Position.Y - _viewportHeight/2 + 10), "Score: " + Score);
            DrawText(basicFont, new Vector2(_camera.Position.X - _viewportWidth/2 + 10, _camera.Position.Y - _viewportHeight/2 + 30), "Time: " + ((int)currentTime / 1000).ToString());
            GameStack.SpriteBatch.End();
            base.Draw(gameTime);
        }

        protected void UpdateLevelObjects(GameTime gameTime)
        {
            for (int i = 0; i < _levelObjects.Count; i++)
            {
                _levelObjects[i].Update(gameTime);
            }
        }


        protected void DrawPlayer()
        {
            Vector2 ballDisplayUnits = ConvertUnits.ToDisplayUnits(player.Position);
            GameStack.SpriteBatch.Draw(ballTexture, ballDisplayUnits, null, Color.White, (float)player.Position.X * 2, new Vector2(ballTexture.Width / 2, ballTexture.Height / 2), 0.5f, SpriteEffects.None, 1.0f);

            //If the player is above the screen, draw a small arrow to indicate position
            if (ballDisplayUnits.Y < 0)
            {
                GameStack.SpriteBatch.Draw(playerTopArrowTexture, new Rectangle((int)ballDisplayUnits.X, 0, playerTopArrowTexture.Width, playerTopArrowTexture.Height), Color.CornflowerBlue);
            }
        }

        //Draws all the objects within each level.
        protected void DrawLevel()
        {
            for (int i = 0; i < _levelObjects.Count; i++)
            {
                if (_levelObjects[i].isActive)
                {
                    _levelObjects[i].Draw(); 
                }
                else
                {
                    gameWorld.RemoveBody(_levelObjects[i].Body);
                    _levelObjects.RemoveAt(i);
                }
            }
        }

        //Draws an object to screen as long as there are no special parameters needed.
        //Best used when iterating through levels objects.
        private void DrawObject(Body body, Texture2D texture)
        {
            Vector2 displayUnits = ConvertUnits.ToDisplayUnits(body.Position);
            GameStack.SpriteBatch.Draw(texture, new Rectangle((int)(displayUnits.X - texture.Width / 2), (int)(displayUnits.Y - texture.Height / 2), texture.Width, texture.Height),  Color.White);
        }


        //Draws text onto level
        private void DrawText(SpriteFont font, Vector2 position, String str)
        {
            GameStack.SpriteBatch.DrawString(font, str, position, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f);
        }
        private void DrawBackground()
        {
            if (backgroundTexture != null)
                GameStack.SpriteBatch.Draw(backgroundTexture, new Rectangle(0, 0, this.Width, this.Height), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0f);
        }

        /// <summary>
        /// Take an image and turn it into a polygon in the world
        /// </summary>
        public static LevelObject ImageToNewObjectPolygon(PhysicsGameScreen screen, Texture2D texture, World world, float density, float scale_)
        {

            Vector2 origin = ConvertUnits.ToSimUnits(new Vector2(texture.Width / 2, texture.Height / 2));
            //Create an array to hold the data from the texture
            uint[] data = new uint[(texture.Width) * (texture.Height)];

            //Collect data from bitmap
            texture.GetData(data);

            //Create Polygon from Bitmap
            Vertices verts = PolygonTools.CreatePolygon(data, (texture.Width), false);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            Vector2 scale = new Vector2(ConvertUnits.ToSimUnits(scale_), ConvertUnits.ToSimUnits(scale_));
            verts.Scale(ref scale);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            //origin = verts.GetCentroid();

            //Translate the polygon so that it aligns properly with centroid.
            Vector2 vertsTranslate = -origin;
            verts.Translate(ref vertsTranslate);

            //Decompose polygon into smaller chuncks that Farseer can process better
            List<Vertices> list;
            list = BayazitDecomposer.ConvexPartition(verts);

            //Create a body
            LevelObject newObject = new LevelObject(screen, texture, BodyFactory.CreateCompoundPolygon(world, list, density), texture.Width, texture.Height);
            return newObject;
        }

        public static Body ImageToPolygonBody(PhysicsGameScreen screen, Texture2D texture, World world, float density, float scale_){
            Vector2 origin = ConvertUnits.ToSimUnits(new Vector2(texture.Width / 2, texture.Height / 2));
            //Create an array to hold the data from the texture
            uint[] data = new uint[(texture.Width) * (texture.Height)];

            //Collect data from bitmap
            texture.GetData(data);

            //Create Polygon from Bitmap
            Vertices verts = PolygonTools.CreatePolygon(data, (texture.Width), false);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            Vector2 scale = new Vector2(ConvertUnits.ToSimUnits(scale_), ConvertUnits.ToSimUnits(scale_));
            verts.Scale(ref scale);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            //origin = verts.GetCentroid();

            //Translate the polygon so that it aligns properly with centroid.
            Vector2 vertsTranslate = -origin;
            verts.Translate(ref vertsTranslate);

            //Decompose polygon into smaller chuncks that Farseer can process better
            List<Vertices> list;
            list = BayazitDecomposer.ConvexPartition(verts);

            //Create a body
            //LevelObject newBody = new LevelObject(screen, texture, BodyFactory.CreateCompoundPolygon(world, list, density), texture.Width, texture.Height);
            return BodyFactory.CreateCompoundPolygon(world, list, density);
        }

        //For objects that act as normal collision
        public static void NormalCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.9f;
        }

        //For objects that add to the balls bounces
        public static void AdditionCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 1.3f;
        }

        //For objects that dampen the balls bounce
        public static void SubtractiveCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.6f;
        }

        ///For objects that take away all bounce
        public static void SlideCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.0f;
        }


        protected void AddObjectToWorld(LevelObject obj, Vector2 position, int type = 0, float depth = 1f, int value = 0, bool entrance = false)
        {
            obj.Body.Position = ConvertUnits.ToSimUnits(position);

            if (type == 0)
                obj.Body.OnCollision += obj.IgnoreCollision;

            else if (type == 1)
                NormalCollision(obj.Body);

            else if (type == 2)
                AdditionCollision(obj.Body);

            else if (type == 3)
                SubtractiveCollision(obj.Body);

            else if (type == 4)
                SlideCollision(obj.Body);
            
            obj.Body.OnCollision += obj.OnCollision;

            if (obj.type() == "door")
                doors.Add(obj);

            if (obj.type() == "collectable")
            {
                obj.ScoreIncrement = value;
            }

            _levelObjects.Add(obj);

            return;
        }

        //Simple way to control the players max speed (threshold)
        protected void adjustPlayerSpeed(GameTime gameTime)
        {
            /*_speedThreshold = 15f * gameTime.ElapsedGameTime.Milliseconds;

            float distanceMoved = Vector2.Distance(Vector2.Zero, player.LinearVelocity) * gameTime.ElapsedGameTime.Milliseconds;
            if (distanceMoved > _speedThreshold)
            {
                Vector2 direction = player.LinearVelocity;
                direction.Normalize();
                player.LinearVelocity = (direction * _speedThreshold);
            }*/

            if (player.LinearVelocity.X > _speedThreshold)
                player.LinearVelocity = new Vector2(_speedThreshold, player.LinearVelocity.Y);
            else if (player.LinearVelocity.X < -_speedThreshold)
                player.LinearVelocity = new Vector2(-_speedThreshold, player.LinearVelocity.Y);

            if (player.LinearVelocity.Y > _speedThreshold)
                player.LinearVelocity = new Vector2(player.LinearVelocity.X, _speedThreshold);
            else if (player.LinearVelocity.Y < -_speedThreshold)
                player.LinearVelocity = new Vector2(player.LinearVelocity.X, -_speedThreshold);

            if (player.LinearVelocity.X > 0)
                player.ApplyForce(-1 * _airResistance);
            else if (player.LinearVelocity.X < 0)
                player.ApplyForce(_airResistance);

            //Checks if player is out of bounds.
            if (player.Position.X < 0 - ConvertUnits.ToSimUnits(250) || player.Position.X > ConvertUnits.ToSimUnits(_screenWidth + 250) || player.Position.Y > ConvertUnits.ToSimUnits(_screenHeight + 100))
                this.isDead = true;
        }

        public bool AddObject(LevelObject obj)
        {
            _levelObjects.Add(obj);
            return true;
        }

    }
}

